package com.li.sqlparse.service.impl;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr;
import com.alibaba.druid.sql.ast.statement.*;
import com.alibaba.druid.sql.dialect.mysql.ast.clause.MySqlDeclareStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
import com.alibaba.druid.stat.TableStat;
import com.li.sqlparse.entity.ParseItemDto;
import com.li.sqlparse.entity.SetDeclareItemDto;
import com.li.sqlparse.entity.SqlConstant;
import com.li.sqlparse.factory.AbstractSqlParseServiceImpl;
import org.apache.commons.collections4.CollectionUtils;

import java.util.*;

/**
 * @author lidong
 */
public class MysqlSqlParseServiceImpl extends AbstractSqlParseServiceImpl {


    /**
     * declare 语句处理
     *
     * @param statement          sql 解析对象
     * @param setFromTables      declare语句中相关的表名称
     * @param setSelectColumns   declare语句中相关的列名称
     * @param setDeclareItemDtos declare语句中相关的对象
     */
    @Override
    public void dealSqlDeclare(SQLStatement statement, Set<String> setFromTables, Set<String> setSelectColumns, List<SetDeclareItemDto> setDeclareItemDtos) {
        MySqlDeclareStatement declareStatement = (MySqlDeclareStatement) statement;
        SQLName name = declareStatement.getVarList().get(0).getName();
        SetDeclareItemDto setDeclareItemDto = new SetDeclareItemDto();
        setDeclareItemDto.setName(String.valueOf(name));
        setDeclareItemDto.setType(SqlConstant.SET);
        setDeclareItemDtos.add(setDeclareItemDto);
    }

    /**
     * set 语句处理
     *
     * @param statement          sql 解析对象
     * @param setFromTables      set语句中相关的表名称
     * @param setSelectColumns   set语句中相关的列名称
     * @param setDeclareItemDtos set语句中相关的对象
     */
    @Override
    public void dealSqlSet(SQLStatement statement, Set<String> setFromTables, Set<String> setSelectColumns, List<SetDeclareItemDto> setDeclareItemDtos) {
        List<SQLAssignItem> items = ((SQLSetStatement) statement).getItems();
        for (SQLAssignItem item : items) {
            String selectColumn = null;
            List<String> fromTables = new ArrayList<>();
            String itemValueSql = String.valueOf(item.getValue());
            if (itemValueSql.toUpperCase().contains(SqlConstant.SELECT)) {
                List<SQLStatement> list = SQLUtils.parseStatements(itemValueSql, "mysql");
                for (SQLStatement sqlStatement : list) {
                    SQLSelectStatement selectStatement = (SQLSelectStatement) sqlStatement;
                    MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
                    Objects.requireNonNull(selectStatement).accept(visitor);
                    Collection<TableStat.Column> columns = visitor.getColumns();
                    for (TableStat.Column column : columns) {
                        // select 后面的字段
                        if (!column.isWhere() && column.isSelect()) {
                            selectColumn = column.getName();
                            setSelectColumns.add(column.getName());
                        }
                        setFromTables.add(column.getTable());
                        if (!fromTables.contains(column.getTable())) {
                            fromTables.add(column.getTable());
                        }
                    }
                }
            }
            SetDeclareItemDto setItemDto = new SetDeclareItemDto();
            setItemDto.setTarget(String.valueOf(item.getTarget()));
            setItemDto.setType(SqlConstant.SET);
            setItemDto.setColumn(selectColumn);
            setItemDto.setFromtable(fromTables);
            setDeclareItemDtos.add(setItemDto);
        }
    }

    /**
     * insert 语句处理
     *
     * @param statement sql 解析对象
     * @param list      sql 解析对象list
     */
    @Override
    public void dealSqlInsert(SQLStatement statement, List<ParseItemDto> list) {
        List<String> sourceColumns = new LinkedList<>();
        List<String> sourceValues = new LinkedList<>();
        SQLExprTableSource tableSource = ((SQLInsertInto) statement).getTableSource();
        // tableSource 后面的column
        List<SQLExpr> columns = ((SQLInsertInto) statement).getColumns();
        for (SQLExpr column : columns) {
            SQLIdentifierExpr sqlIdentifierExpr = (SQLIdentifierExpr) column;
            sourceColumns.add(sqlIdentifierExpr.getName());
        }
        // valuelist
        List<SQLInsertStatement.ValuesClause> valuesList = ((SQLInsertInto) statement).getValuesList();
        if (CollectionUtils.isNotEmpty(valuesList)) {
            List<SQLExpr> values = valuesList.get(0).getValues();
            for (SQLExpr value : values) {
                if (value instanceof SQLPropertyExpr) {
                    SQLPropertyExpr sqlPropertyExpr = (SQLPropertyExpr) value;
                    sourceValues.add(sqlPropertyExpr.getName());
                }
                if (value instanceof SQLIdentifierExpr) {
                    SQLIdentifierExpr sqlIdentifierExpr = (SQLIdentifierExpr) value;
                    sourceValues.add(sqlIdentifierExpr.getName());
                }
                if (value instanceof SQLVariantRefExpr) {
                    SQLVariantRefExpr sqlVariantRefExpr = (SQLVariantRefExpr) value;
                    sourceValues.add(sqlVariantRefExpr.getName());
                }
            }
        }
        ParseItemDto parseItem = new ParseItemDto();
        String id = String.valueOf(UUID.randomUUID());
        parseItem.setId(id);
        parseItem.setTo(String.valueOf(tableSource));
        parseItem.setValues(sourceValues);
        parseItem.setSqlType(SqlConstant.INSERT);
        if (CollectionUtils.isEmpty(valuesList)) {
            parseItem.setSqlType(SqlConstant.INSERT_INTO_SELECT);
        }
        SQLSelect query = ((SQLInsertInto) statement).getQuery();
        // insert into 表后面没有跟column column 是select获的得
        if (CollectionUtils.isEmpty(sourceColumns)) {
            SQLSelectQuery selectQuery = query.getQuery();
            List<SQLSelectItem> selectList = ((SQLSelectQueryBlock) selectQuery).getSelectList();
            for (SQLSelectItem sqlSelectItem : selectList) {
                sourceColumns.add(String.valueOf(sqlSelectItem));
            }
        }
        if (Objects.nonNull(query)) {
            parseItem.setFrom(String.valueOf(query));
        }
        parseItem.setColumns(sourceColumns);
        list.add(parseItem);
    }


    /**
     * create 语句处理
     *
     * @param statement statement
     * @param list      ParseItem List
     */
    @Override
    public void dealSqlCreate(SQLStatement statement, List<ParseItemDto> list) {
        ParseItemDto parseItem = new ParseItemDto();
        parseItem.setSqlType(SqlConstant.CREATE);
        String id = String.valueOf(UUID.randomUUID());
        parseItem.setId(id);
        List<String> sourcevalues = new LinkedList<>();
        MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) statement;
        SQLExprTableSource tableSource = createTableStatement.getTableSource();
        SQLSelect select = createTableStatement.getSelect();
        if (Objects.nonNull(select)) {
            SQLSelectQuery query = select.getQuery();
            MySqlSelectQueryBlock selectQueryBlock = (MySqlSelectQueryBlock) query;
            List<SQLSelectItem> selectList = selectQueryBlock.getSelectList();
            for (SQLSelectItem sqlSelectItem : selectList) {
                sourcevalues.add(String.valueOf(sqlSelectItem));
            }
            String from = String.valueOf(selectQueryBlock.getFrom());
            parseItem.setFrom(from);
            if (from.contains(SqlConstant.SELECT)) {
                List<ParseItemDto> children = dealParseItems(parseItem, from, new ArrayList<>());
                parseItem.setChildren(children);
            }
        }
        parseItem.setTo(String.valueOf(tableSource));
        parseItem.setColumns(sourcevalues);
        list.add(parseItem);
    }

    /**
     * 递归处理 PatseItems
     *
     * @param parseItem parseItem
     * @param sql       sql
     * @param list      递归得到的 children list
     * @return List<ParseItem>  List<ParseItem>
     */
    @Override
    public List<ParseItemDto> dealParseItems(ParseItemDto parseItem, String sql, List<ParseItemDto> list) {
        String from = parseItem.getFrom();
        String id = parseItem.getId();
        if (Objects.nonNull(from) && from.contains(SqlConstant.SELECT)) {
            ParseItemDto item = dealSelectSql(from, id);
            list.add(item);
            dealParseItems(item, null, list);
        }
        return list;
    }

    /**
     * 处理 select insert 语句
     *
     * @param statement  statement
     * @param parseItems parseItems
     */
    @Override
    public void dealSqlSelectInto(SQLStatement statement, List<ParseItemDto> parseItems) {
        SQLSelectQuery query = ((SQLSelectStatement) statement).getSelect().getQuery();
        SQLSelectQueryBlock sqlSelectQueryBlock = (SQLSelectQueryBlock) query;
        ParseItemDto parseItem = new ParseItemDto();
        parseItem.setId(String.valueOf(UUID.randomUUID()));
        String from = String.valueOf(sqlSelectQueryBlock.getFrom());
        parseItem.setTo(String.valueOf(sqlSelectQueryBlock.getInto()));
        List<SQLSelectItem> selectList = sqlSelectQueryBlock.getSelectList();
        List<String> columns = new ArrayList<>();
        for (SQLSelectItem sqlSelectItem : selectList) {
            columns.add(String.valueOf(sqlSelectItem));
        }
        if (from.contains(SqlConstant.SELECT)) {
            ParseItemDto item = new ParseItemDto();
            String uid = String.valueOf(UUID.randomUUID());
            parseItem.setId(uid);
            parseItem.setFrom(from);
            List<ParseItemDto> items = dealParseItems(item, null, new ArrayList<>());
            parseItem.setChildren(items);
        } else if (sqlSelectQueryBlock.getFrom() instanceof SQLJoinTableSource) {
            SQLTableSource sqlTableSource = sqlSelectQueryBlock.getFrom();
            SQLJoinTableSource sqlJoinTableSource = ((SQLJoinTableSource) sqlTableSource);
            SQLTableSource left = sqlJoinTableSource.getLeft();
            SQLTableSource right = sqlJoinTableSource.getRight();
            List<Map<String, String>> tableInfos = new ArrayList<>();
            Map<String, String> mapRight = new HashMap<>(16);
            mapRight.put(KEY_TABLE, String.valueOf(((SQLExprTableSource) right).getExpr()));
            mapRight.put(KEY_ALIAS, right.getAlias());
            tableInfos.add(mapRight);
            dealJoinLeftRight(left, tableInfos);
            parseItem.setJoinTableInfo(tableInfos);
        } else {
            parseItem.setFrom(from);
        }
        parseItem.setSqlType(SqlConstant.SELECT_INTO);
        parseItem.setColumns(columns);
        parseItems.add(parseItem);
    }

    /**
     * 处理 UNION 子句
     *
     * @param item           item
     * @param sqlTableSource SQLTableSource
     */
    @Override
    public final void dealSqlUnionQueryTableSource(ParseItemDto item, SQLTableSource sqlTableSource) {
        SQLUnionQuery union = ((SQLUnionQueryTableSource) sqlTableSource).getUnion();
        List<SQLSelectQuery> relations = union.getRelations();
        for (SQLSelectQuery relation : relations) {
            ParseItemDto parseItem = new ParseItemDto();
            String uid = String.valueOf(UUID.randomUUID());
            parseItem.setId(uid);
            parseItem.setFrom(String.valueOf(relation));
            List<ParseItemDto> parseItems = dealParseItems(parseItem, null, new ArrayList<>());
            if (Objects.isNull(item.getChildren())) {
                item.setChildren(parseItems);
            } else {
                parseItems.forEach(x -> item.getChildren().add(x));
            }
        }
    }

    /**
     * 处理 JoinTableInfo
     *
     * @param item  ParseItem
     * @param froms SQLTableSource
     */
    @Override
    public void dealJoinTableInfo(ParseItemDto item, SQLTableSource froms) {
        SQLJoinTableSource sqlJoinTableSource = ((SQLJoinTableSource) froms);
        SQLTableSource left = sqlJoinTableSource.getLeft();
        SQLTableSource right = sqlJoinTableSource.getRight();
        List<Map<String, String>> tableInfos = new ArrayList<>();
        Map<String, String> mapRight = new HashMap<>(16);
        mapRight.put(KEY_TABLE, String.valueOf(((SQLExprTableSource) right).getExpr()));
        mapRight.put(KEY_ALIAS, right.getAlias());
        tableInfos.add(mapRight);
        dealJoinLeftRight(left, tableInfos);
        item.setJoinTableInfo(tableInfos);
    }


    /**
     * 处理 Select 语句
     *
     * @param sql      sql
     * @param parentid 上个 parseItem的id
     * @return ParseItem  ParseItem
     */
    private ParseItemDto dealSelectSql(String sql, String parentid) {
        ParseItemDto item = new ParseItemDto();
        String id = String.valueOf(UUID.randomUUID());
        item.setId(id);
        item.setParentid(parentid);
        List<SQLStatement> list = SQLUtils.parseStatements(sql, "mysql");
        List<String> selectColumn = new ArrayList<>();
        String from = "";
        for (SQLStatement sqlStatement : list) {
            SQLSelectQuery query = ((SQLSelectStatement) sqlStatement).getSelect().getQuery();
            // union子句处理
            if (query instanceof SQLUnionQuery) {
                dealSqlUnionQuery(query, item);
            }
            if (query instanceof MySqlSelectQueryBlock) {
                SQLTableSource froms = ((SQLSelectQueryBlock) query).getFrom();
                // join 语句处理
                if (froms instanceof SQLJoinTableSource) {
                    List<SQLSelectItem> selectList = ((SQLSelectQueryBlock) query).getSelectList();
                    for (SQLSelectItem sqlSelectItem : selectList) {
                        selectColumn.add(String.valueOf(sqlSelectItem));
                    }
                    dealJoinTableInfo(item, froms);
                }
                // select column类型的处理
                if (froms instanceof SQLSubqueryTableSource) {
                    List<SQLSelectItem> selectList = ((SQLSelectQueryBlock) query).getSelectList();
                    for (SQLSelectItem sqlSelectItem : selectList) {
                        selectColumn.add(String.valueOf(sqlSelectItem));
                    }
                    dealSqlSubqueryTableSource(item, query);
                }
                // select  后面有union语句处理
                if (froms instanceof SQLUnionQueryTableSource) {
                    List<SQLSelectItem> selectList = ((SQLSelectQueryBlock) query).getSelectList();
                    for (SQLSelectItem sqlSelectItem : selectList) {
                        selectColumn.add(String.valueOf(sqlSelectItem));
                    }
                    dealSqlUnionQueryTableSource(item, froms);
                }
                if (froms instanceof SQLExprTableSource) {
                    from = String.valueOf(((SQLSelectQueryBlock) query).getFrom());
                    List<SQLSelectItem> selectList = ((SQLSelectQueryBlock) query).getSelectList();
                    for (SQLSelectItem sqlSelectItem : selectList) {
                        selectColumn.add(String.valueOf(sqlSelectItem));
                    }
                }

            }
        }
        item.setSqlType(SqlConstant.SELECT);
        item.setFrom(from);
        item.setColumns(selectColumn);
        return item;
    }

    /**
     * 处理 select 中union 语句
     *
     * @param query SQLSelectQuery
     * @param item  ParseItem
     */
    private void dealSqlUnionQuery(SQLSelectQuery query, ParseItemDto item) {
        List<ParseItemDto> sqlUnionQuerys = new ArrayList<>();
        SQLUnionQuery sqlUnionQuery = (SQLUnionQuery) query;
        SQLSelectQuery left = sqlUnionQuery.getLeft();
        // union 左子句解析对象不为空
        if (Objects.nonNull(left)) {
            ParseItemDto leftParseItem = new ParseItemDto();
            String leftParseItemId = String.valueOf(UUID.randomUUID());
            leftParseItem.setId(leftParseItemId);
            leftParseItem.setParentid(item.getId());
            MySqlSelectQueryBlock leftBlock = (MySqlSelectQueryBlock) left;
            List<SQLSelectItem> selectList = leftBlock.getSelectList();
            List<String> leftSelectList = new ArrayList<>();
            selectList.forEach(x -> leftSelectList.add(String.valueOf(x)));
            leftParseItem.setColumns(leftSelectList);
            String leftBlockFrom = Optional.ofNullable(String.valueOf(leftBlock.getFrom())).orElse(null);
            leftParseItem.setFrom(String.valueOf(leftBlock.getFrom()));
            if (Objects.nonNull(leftBlockFrom) && leftBlockFrom.toUpperCase().contains(SqlConstant.SELECT)) {
                List<ParseItemDto> leftParseItemChild = new ArrayList<>();
                ParseItemDto parseItem = dealSelectSql(leftBlockFrom, leftParseItemId);
                leftParseItemChild.add(parseItem);
                leftParseItem.setChildren(leftParseItemChild);
            }
            sqlUnionQuerys.add(leftParseItem);
        }
        SQLSelectQuery right = sqlUnionQuery.getRight();
        // 处理 union 右子句
        if (Objects.nonNull(right)) {
            ParseItemDto rightParseItem = new ParseItemDto();
            String rightParseItemId = String.valueOf(UUID.randomUUID());
            rightParseItem.setId(rightParseItemId);
            rightParseItem.setParentid(item.getId());
            MySqlSelectQueryBlock rightBlock = (MySqlSelectQueryBlock) left;
            if (Objects.nonNull(rightBlock)) {
                List<SQLSelectItem> selectList = rightBlock.getSelectList();
                List<String> rightSelectList = new ArrayList<>();
                selectList.forEach(x -> rightSelectList.add(String.valueOf(x)));
                rightParseItem.setColumns(rightSelectList);
                String rightBlockFrom = String.valueOf(Optional.ofNullable(rightBlock.getFrom()).orElse(null));
                rightParseItem.setFrom(String.valueOf(rightBlock.getFrom()));
                if (Objects.nonNull(rightBlockFrom) && rightBlockFrom.toUpperCase().contains(SqlConstant.SELECT)) {
                    List<ParseItemDto> rightParseItemChild = new ArrayList<>();
                    ParseItemDto parseItem = dealSelectSql(rightBlockFrom, rightParseItemId);
                    rightParseItemChild.add(parseItem);
                    rightParseItem.setChildren(rightParseItemChild);
                }
            }
            sqlUnionQuerys.add(rightParseItem);
        }
        if (CollectionUtils.isNotEmpty(sqlUnionQuerys)) {
            item.setChildren(sqlUnionQuerys);
        }
    }


    /**
     * 处理 leftjoin 语句 得到 table 别名
     *
     * @param left       SQLTableSource
     * @param tableInfos 相关table和别名的信息
     */
    @Override
    public void dealJoinLeftRight(SQLTableSource left, List<Map<String, String>> tableInfos) {
        if (left instanceof SQLExprTableSource) {
            Map<String, String> map = new HashMap<>(16);
            String tableName = String.valueOf(left);
            map.put(KEY_TABLE, tableName);
            map.put(KEY_ALIAS, Optional.ofNullable(left.getAlias()).orElse(tableName));
            tableInfos.add(map);
        }
        if (left instanceof SQLJoinTableSource) {
            SQLTableSource leftOne = ((SQLJoinTableSource) left).getLeft();
            String leftSql = String.valueOf(leftOne).toUpperCase(Locale.ROOT);
            if (((SQLJoinTableSource) left).getLeft() instanceof SQLExprTableSource) {
                SQLExprTableSource exprTableSource = (SQLExprTableSource) ((SQLJoinTableSource) left).getLeft();
                Map<String, String> map = new HashMap<>(16);
                String tableName = String.valueOf(exprTableSource.getExpr());
                map.put(KEY_TABLE, tableName);
                map.put(KEY_ALIAS, Optional.ofNullable(exprTableSource.getAlias()).orElse(tableName));
                tableInfos.add(map);
            }
            if (((SQLJoinTableSource) left).getRight() instanceof SQLExprTableSource) {
                SQLExprTableSource exprTableSource = (SQLExprTableSource) ((SQLJoinTableSource) left).getRight();
                Map<String, String> map = new HashMap<>(16);
                String tableName = String.valueOf(exprTableSource.getExpr());
                map.put(KEY_TABLE, tableName);
                map.put(KEY_ALIAS, Optional.ofNullable(exprTableSource.getAlias()).orElse(tableName));
                tableInfos.add(map);
            }
            if (leftSql.toUpperCase().contains(SqlConstant.JOIN)) {
                dealJoinLeftRight(leftOne, tableInfos);
            }
        }
    }

    /**
     * from 为 SqlSubqueryTableSource 处理Children
     *
     * @param item  item
     * @param query SQLSelectQuery
     */
    @Override
    public void dealSqlSubqueryTableSource(ParseItemDto item, SQLSelectQuery query) {
        String from = String.valueOf(((SQLSelectQueryBlock) query).getFrom());
        if (from.contains(SqlConstant.SELECT)) {
            ParseItemDto parseItem = new ParseItemDto();
            String uid = String.valueOf(UUID.randomUUID());
            parseItem.setId(uid);
            parseItem.setFrom(from);
            List<ParseItemDto> parseItems = dealParseItems(parseItem, null, new ArrayList<>());
            item.setChildren(parseItems);
        }
    }


}